# Copyright 2014-present PlatformIO <contact@platformio.org>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Arduino

Arduino Wiring-based Framework allows writing cross-platform software to
control devices attached to a wide range of Arduino boards to create all
kinds of creative coding, interactive objects, spaces or physical experiences.

"""

import sys
from os.path import isfile, isdir, join

from SCons.Script import DefaultEnvironment

env = DefaultEnvironment()
platform = env.PioPlatform()
board_config = env.BoardConfig()

IS_WINDOWS = sys.platform.startswith("win")
FRAMEWORK_DIR = platform.get_package_dir("framework-arduino-wchch32v")
assert isdir(FRAMEWORK_DIR)

mcu = board_config.get("build.mcu", "")
mcu_type = mcu[:-2]
variant = board_config.get("build.variant")
series = mcu_type[:6].upper() + "xx"
variants_dir = (
    join("$PROJECT_DIR", board_config.get("build.variants_dir"))
    if board_config.get("build.variants_dir", "")
    else join(FRAMEWORK_DIR, "variants")
)
variant_dir = join(variants_dir, variant)
inc_variant_dir = variant_dir
if not IS_WINDOWS and not env.IsIntegrationDump and " " not in variant_dir:
    inc_variant_dir = variant_dir.replace("(", r"\(").replace(")", r"\)")

upload_protocol = env.subst("$UPLOAD_PROTOCOL")

def configure_application_offset(mcu, upload_protocol):
    offset = 0

    if upload_protocol == "hid":
        offset = 0x800
        env.Append(CPPDEFINES=["BL_HId"])
    
    elif upload_protocol == "dfu":
        offset = 0x5000
        env.Append(CPPDEFINES=["BL_LEGACY_LEAF"])
    
    if offset != 0:
        env.Append(CPPDEFINES=[("VECT_TAB_OFFSET", "%s" % hex(offset))],)
    # LD_FLASH_OFFSET is mandatory even if there is no offset
    env.Append(LINKFLAGS=["-Wl,--defsym=LD_FLASH_OFFSET=%s" % hex(offset)])

def get_arduino_board_id(board_config, mcu):
    if board_config.get("build.arduino.board", ""):
        return board_config.get("build.arduino.board")
    
    board_id = env.subst("$BOARD")

    if board_id.lower().startswith("generic"):
        board_id = "GENERIC_"
        mcu = mcu.upper()
        if len(mcu) > 11:
            board_id += mcu[4:11] + "X"
        else:
            if len(mcu) > 10:
                board_id += mcu[4:10] + "TX"
            else:
                board_id += mcu
            print(
                    "Warning! Couldn't generate proper internal board id from the `%s` MCU "
                    "field! At least 11 symbols are required!" % mcu
                )

            print("Falling back to `%s`." % board_id)

    return board_id.upper()

board_id = get_arduino_board_id(board_config, mcu)
machine_flags = [
    "-march=%s" % board_config.get("build.march", "rv32imac"),
    "-mabi=%s" % board_config.get("build.mabi",  "ilp32"), 
    "-mcmodel=%s" % board_config.get("build.mcmodel", "medlow"),
]

env.Append(
    ASFLAGS=machine_flags,
    ASPPFLAGS=["-x", "assembler-with-cpp"],
    CFLAGS=["-std=gnu11"],
    CXXFLAGS=[
        "-std=gnu++14",
        "-fno-threadsafe-statics",
        "-fno-rtti",
        "-fno-exceptions",
        "-fno-use-cxa-atexit",
        "-fno-common",
    ],
    CCFLAGS=machine_flags + [
        "-Os",
        "-msmall-data-limit=8",
        "-mno-save-restore",
        "-fmessage-length=0",
        "-fsigned-char",
        "-ffunction-sections",
        "-fdata-sections",
        "-nostdlib",
        "--param", 
        "ggc-min-heapsize=4096",
    ],
    CPPDEFINES=[
        series,
        ("ARDUINO", 10808),
        "ARDUINO_ARCH_RISCV32",
        "ARDUINO_%s" % board_id,
        ("BOARD_NAME", '\\"%s\\"' % board_id),
        (
            "VARIANT_H",
            '\\"%s\\"'
            % board_config.get(
                "build.arduino.variant_h",
                "variant_%s.h" % ("generic" if board_id.lower().startswith("generic") else board_id),
            ) 
        ),
        "NDEBUG",
    ],
    CPPPATH=[
        join(FRAMEWORK_DIR, "cores", "arduino", "avr"),
        join(FRAMEWORK_DIR, "cores", "arduino", "ch32v"),
        join(FRAMEWORK_DIR, "cores", "arduino", "ch32v", "riscv"),
        join(FRAMEWORK_DIR, "cores", "arduino", "ch32v", "usb"),
        join(FRAMEWORK_DIR, "cores", "arduino", "ch32v", "usb", "cdc"),
        join(FRAMEWORK_DIR, "system", "Drivers", series, "Peripheral", "inc"),
        join(FRAMEWORK_DIR, "system", "Drivers", series, "Peripheral", "src"),
        join(FRAMEWORK_DIR, "system", series),
        join(
            FRAMEWORK_DIR,
            "system",
            "Drivers",
            "CMSIS",
            "WCH",
            series,
            "Include"
        ),
        join(
            FRAMEWORK_DIR,
            "system",
            "Drivers",
            "CMSIS",
            "WCH",
            series,
            "Source"
        ),
        join(FRAMEWORK_DIR, "cores", "arduino"),
        variant_dir,
    ],
    LINKFLAGS=machine_flags + [
        "-Os",
        "-msmall-data-limit=8",
        "-mno-save-restore",
        "-nostartfiles",
        "--specs=nano.specs",
        "--specs=nosys.specs",
        "-Xlinker",
        "--gc-sections",
        "-Wl,--defsym=LD_MAX_SIZE=%d" % board_config.get("upload.maximum_size"),
        "-Wl,--defsym=LD_MAX_DATA_SIZE=%d" % board_config.get("upload.maximum_ram_size"),
    ],
    LIBS=[
        "c",
        "m",
        "gcc",
        "stdc++",
        "c_nano",
        "nosys",
        "printf",
    ],
    LIBPATH=[variant_dir],
)
env.ProcessFlags(board_config.get("build.framework_extra_flags.arduino", ""))
configure_application_offset(mcu, upload_protocol)

#
# Linker requires preprocessing with correct RAM|ROM sizes
#

if not board_config.get("build.ldscript", ""):
    env.Replace(LDSCRIPT_PATH=join(FRAMEWORK_DIR, "system", "ldscript.ld"))
    if not isfile(join(env.subst(variant_dir), "ldscript.ld")):
        print("Warning! Cannot find linker script for the current target!\n")
    env.Append(LINKFLAGS=[
        (
            "-Wl,--default-script", 
            join(
                inc_variant_dir, 
                board_config.get("build.arduino.ldscript", "ldscript.ld")
            )
        )]
    )

# copy CCFLAGS to ASFLAGS (-x assembler-with-cpp mode)
env.Append(ASFLAGS=env.get("CCFLAGS", [])[:])

env.Append(
    LIBSOURCE_DIRS=[
        join(FRAMEWORK_DIR, "libraries", "__cores__", "arduino"),
        join(FRAMEWORK_DIR, "libraries"),
    ]
)

if board_config.get("build.on_board_ethnet", 0):
    env.Append(
        CPPPATH=[join("$PROJECT_DIR", "src")],
        LIBSOURCE_DIRS=[
            join(FRAMEWORK_DIR, "system", "Drivers", "WCH_SDK")
        ],
        LIBS=["wchnet", "wchnet_float"],
        LIBPATH=[
            join(
                FRAMEWORK_DIR,
                "system",
                "Drivers",
                "WCH_SDK",
                "NetLib",
            )
        ],
    )

#
# Target: Build Core Library
#

libs = []

if "build.variant" in board_config:
    env.Append(CPPPATH=[inc_variant_dir], LIBPATH=[inc_variant_dir])
    env.BuildSources(join("$BUILD_DIR", "FrameworkArduinoVariant"), variant_dir)

libs.append(
    env.BuildLibrary(
        join("$BUILD_DIR", "FrameworkArduino"), join(FRAMEWORK_DIR, "cores", "arduino")
    )
)

env.BuildSources(
    join("$BUILD_DIR", "SrcWrapper"), join(FRAMEWORK_DIR, "libraries", "SrcWrapper")
)

env.Prepend(LIBS=libs)